﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;

namespace ALinq.Dynamic
{
    class QueryableParser
    {
        [DebuggerDisplay("{id},{text},{pos}")]
        private struct Token
        {
            public TokenId id;
            public string text;
            public int pos;
        }

        enum TokenId
        {
            Unknown,
            End,
            Identifier,
            StringLiteral,
            IntegerLiteral,
            RealLiteral,
            Exclamation,
            Percent,
            Amphersand,
            OpenParen,
            CloseParen,
            Asterisk,
            Plus,
            Comma,
            Minus,
            Dot,
            Slash,
            Colon,
            LessThan,
            Equal,
            GreaterThan,
            Question,
            OpenBracket,
            CloseBracket,
            Bar,
            ExclamationEqual,
            DoubleAmphersand,
            LessThanEqual,
            LessGreater,
            DoubleEqual,
            GreaterThanEqual,
            DoubleBar,
            Sharp,
            OpenCurlyBrace,
            CloseCurlyBrace
        }

        Token token;

        string text;
        int textPos;
        int textLen;

        int keywordPos;

        char ch;            //當前字符

        static Dictionary<string, object> keywords;

        private const string KEYWORD_SELECT = "SELECT";
        private const string KEYWORD_FROM = "FROM";

        public QueryableParser(string query, params object[] values)
        {
            if (string.IsNullOrEmpty(query))
                throw new ArgumentNullException("query");

            this.text = query;
            this.textLen = query.Length;

            if (keywords == null)
                keywords = CreateKeywords();
        }

        static Dictionary<string, object> CreateKeywords()
        {
            Dictionary<string, object> d = new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase);

            d.Add("select", KEYWORD_SELECT);
            d.Add("from", KEYWORD_FROM);

            //d.Add("true", trueLiteral);
            //d.Add("false", falseLiteral);
            //d.Add("null", nullLiteral);
            //d.Add(keywordIt, keywordIt);
            //d.Add(keywordIif, keywordIif);
            //d.Add(keywordNew, keywordNew);
            //d.Add(keywordThis, keywordThis);

            //foreach (Type type in predefinedTypes) d.Add(type.Name, type);
            return d;
        }

        void SetTextPos(int pos)
        {
            textPos = pos;
            ch = textPos < textLen ? text[textPos] : '\0';
        }

        void NextChar()
        {
            if (textPos < textLen) textPos++;
            ch = textPos < textLen ? text[textPos] : '\0';
        }

        bool NextKeyWord()
        {
            keywordPos = token.pos;
            do
            {
                NextToken();
                object value;
                keywords.TryGetValue(token.text, out value);
                if (value != null)
                    return true;
            }
            while (token.id != TokenId.End);

            return false;
        }

        #region NextToken 方法
        void NextToken()
        {
            while (Char.IsWhiteSpace(ch))
                NextChar();
            TokenId t;
            int tokenPos = textPos;
            switch (ch)
            {
                case '!':
                    NextChar();
                    if (ch == '=')
                    {
                        NextChar();
                        t = TokenId.ExclamationEqual;
                    }
                    else
                    {
                        t = TokenId.Exclamation;
                    }
                    break;
                case '%':
                    NextChar();
                    t = TokenId.Percent;
                    break;
                case '&':
                    NextChar();
                    if (ch == '&')
                    {
                        NextChar();
                        t = TokenId.DoubleAmphersand;
                    }
                    else
                    {
                        t = TokenId.Amphersand;
                    }
                    break;
                case '(':
                    NextChar();
                    t = TokenId.OpenParen;
                    break;
                case ')':
                    NextChar();
                    t = TokenId.CloseParen;
                    break;
                case '*':
                    NextChar();
                    t = TokenId.Asterisk;
                    break;
                case '+':
                    NextChar();
                    t = TokenId.Plus;
                    break;
                case ',':
                    NextChar();
                    t = TokenId.Comma;
                    break;
                case '-':
                    NextChar();
                    t = TokenId.Minus;
                    break;
                case '.':
                    NextChar();
                    t = TokenId.Dot;
                    break;
                case '/':
                    NextChar();
                    t = TokenId.Slash;
                    break;
                case ':':
                    NextChar();
                    t = TokenId.Colon;
                    break;
                case '<':
                    NextChar();
                    if (ch == '=')
                    {
                        NextChar();
                        t = TokenId.LessThanEqual;
                    }
                    else if (ch == '>')
                    {
                        NextChar();
                        t = TokenId.LessGreater;
                    }
                    else
                    {
                        t = TokenId.LessThan;
                    }
                    break;
                case '=':
                    NextChar();
                    if (ch == '=')
                    {
                        NextChar();
                        t = TokenId.DoubleEqual;
                    }
                    else
                    {
                        t = TokenId.Equal;
                    }
                    break;
                case '>':
                    NextChar();
                    if (ch == '=')
                    {
                        NextChar();
                        t = TokenId.GreaterThanEqual;
                    }
                    else
                    {
                        t = TokenId.GreaterThan;
                    }
                    break;
                case '?':
                    NextChar();
                    t = TokenId.Question;
                    break;
                case '[':
                    NextChar();
                    t = TokenId.OpenBracket;
                    break;
                case ']':
                    NextChar();
                    t = TokenId.CloseBracket;
                    break;
                case '|':
                    NextChar();
                    if (ch == '|')
                    {
                        NextChar();
                        t = TokenId.DoubleBar;
                    }
                    else
                    {
                        t = TokenId.Bar;
                    }
                    break;
                case '"':
                case '\'':
                    char quote = ch;
                    do
                    {
                        NextChar();
                        while (textPos < textLen && ch != quote) NextChar();
                        if (textPos == textLen)
                            throw ParseError(textPos, Res.UnterminatedStringLiteral);
                        NextChar();
                    } while (ch == quote);
                    t = TokenId.StringLiteral;
                    break;
                case '#':
                    char sharp = ch;
                    do
                    {
                        NextChar();
                        while (textPos < textLen && ch != sharp) NextChar();
                        if (textPos == textLen)
                            throw ParseError(textPos, Res.UnterminatedStringLiteral);
                        NextChar();
                    } while (ch == sharp);
                    t = TokenId.Sharp;
                    break;
                case '{':
                    NextChar();
                    t = TokenId.OpenCurlyBrace;
                    break;
                case '}':
                    NextChar();
                    t = TokenId.CloseCurlyBrace;
                    break;
                default:
                    if (Char.IsLetter(ch) || ch == '@' || ch == '_')
                    {
                        do
                        {
                            NextChar();
                        } while (Char.IsLetterOrDigit(ch) || ch == '_');
                        t = TokenId.Identifier;
                        break;
                    }
                    if (Char.IsDigit(ch))
                    {
                        t = TokenId.IntegerLiteral;
                        do
                        {
                            NextChar();
                        } while (Char.IsDigit(ch));
                        if (ch == '.')
                        {
                            t = TokenId.RealLiteral;
                            NextChar();
                            ValidateDigit();
                            do
                            {
                                NextChar();
                            } while (Char.IsDigit(ch));
                        }
                        if (ch == 'E' || ch == 'e')
                        {
                            t = TokenId.RealLiteral;
                            NextChar();
                            if (ch == '+' || ch == '-') NextChar();
                            ValidateDigit();
                            do
                            {
                                NextChar();
                            } while (Char.IsDigit(ch));
                        }
                        if (ch == 'F' || ch == 'f') NextChar();
                        break;
                    }
                    if (textPos == textLen)
                    {
                        t = TokenId.End;
                        break;
                    }
                    throw ParseError(textPos, Res.InvalidCharacter, ch);
            }

            token.id = t;
            token.text = text.Substring(tokenPos, textPos - tokenPos);
            token.pos = tokenPos;
        }
        #endregion

        void ValidateDigit()
        {
            if (!Char.IsDigit(ch)) throw ParseError(textPos, Res.DigitExpected);
        }

        public IQueryable Parse()
        {
            return Parse(null);
        }

        public IQueryable Parse(Type elementType)
        {
            SetTextPos(0);
            while (NextKeyWord())
            {
                switch (token.text.ToUpper())
                {
                    case KEYWORD_SELECT:
                        ParseSelect();
                        break;

                    case KEYWORD_FROM:
                        ParseFrom();
                        break;
                }
            }
            return null;
        }

        private void ParseIdentifier()
        {
            ValidateToken(TokenId.Identifier);
            object value;
            if (keywords.TryGetValue(token.text, out value))
            {
                var keyword = value as string;
                switch (keyword)
                {
                    case KEYWORD_SELECT:
                        ParseSelect();
                        return;
                }
            }

            throw ParseError(Res.UnknownIdentifier, token.text);
        }


        private void ParseFrom()
        {
            var pos = token.pos + token.text.Length;
            ValidateKeyword(KEYWORD_FROM);
            NextKeyWord();

            if (token.id != TokenId.End)
                throw ParseError(Res.SyntaxError);

            var fromText = text.Substring(pos, token.pos - pos);
            //TODO:將 fromText 解釋成 Expression
        }

        private void ParseSelect()
        {
            var pos = token.pos + token.text.Length;
            ValidateKeyword(KEYWORD_SELECT);
            NextKeyWord();

            var selectionText = text.Substring(pos, token.pos - pos);
            //TODO:將 selectionText 解釋成為 Expression

            ParseFrom();
        }

        Exception ParseError(string format, params object[] args)
        {
            return ParseError(token.pos, format, args);
        }

        Exception ParseError(int pos, string format, params object[] args)
        {
            return new ParseException(string.Format(System.Globalization.CultureInfo.CurrentCulture, format, args), pos);
        }

        void ValidateToken(TokenId t)
        {
            if (token.id != t) throw ParseError(Res.SyntaxError);
        }

        void ValidateKeyword(string keyword)
        {
            if (!string.Equals(token.text, keyword, StringComparison.OrdinalIgnoreCase))
                throw ParseError(Res.SyntaxError);
        }
    }
}
